package org.alicuu.dao;

import org.alicuu.toolkit.ReflectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: Danny
 * Date: 16/07/06
 * Time: 19:04
 */
public abstract class Query implements Cloneable {
    public static final Logger log = LoggerFactory.getLogger(Query.class);

    /**
     * Encapsulate JDBC operation to template.
     *
     * @param sql
     * @param params
     * @param clazz
     * @param callback
     * @return
     */
    private Object executeQueryTemplate(String sql, Object[] params, Class clazz, CallBack callback) {
        Connection connection = DBManager.getConnection();
        PreparedStatement ps = null;
        ResultSet rs;
        try {
            ps = connection.prepareStatement(sql);
            JDBCUtil.handleParams(ps, params);
            rs = ps.executeQuery();
            return callback.doExecute(connection, ps, rs);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        } finally {
            DBManager.close(null, ps, connection);
        }
    }

    /**
     * DML
     */
    private int execute(String sql, Object[] params) {
        Connection conn = DBManager.getConnection();
        int modCount = 0;
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(sql);
            JDBCUtil.handleParams(ps, params);
            modCount = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBManager.close(null, ps, conn);
        }

        return modCount;
    }


    public void save(Object object) {
        Class clazz = object.getClass();
        List<Object> params = new ArrayList<>();
        TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
        StringBuilder sql = new StringBuilder("INSERT INTO " + tableInfo.getTableName() + " (");
        Field[] fields = clazz.getDeclaredFields();
        int fieldNotNllCount = 0;

        for (Field field : fields) {
            String fieldName = field.getName();
            Object fieldValue = ReflectUtil.invokeGetter(fieldName, object);
            if (fieldValue != null) {
                fieldNotNllCount++;
                sql.append(fieldName).append(",");
                params.add(fieldValue);
            }
        }

        sql.setCharAt(sql.length() - 1, ')');
        sql.append(" values (");
        for (int i = 0; i < fieldNotNllCount; i++)
            sql.append("?,");

        sql.setCharAt(sql.length() - 1, ')');

        this.execute(sql.toString(), params.toArray());
    }

    private int delete(Class clazz, Object id) {
        TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
        ColumnInfo onlyPrimaryKey = tableInfo.getPrimaryKey();
        String sql = "DELETE FROM " + tableInfo.getTableName() + " WHERE " + onlyPrimaryKey.getColName() + "=?";
        return execute(sql, new Object[]{id});
    }

    public int delete(Object obj) {
        Class clazz = obj.getClass();
        TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
        ColumnInfo onlyPrimaryKey = tableInfo.getPrimaryKey();

        Object priKeyValue = ReflectUtil.invokeGetter(onlyPrimaryKey.getColName(), null);
        return delete(clazz, priKeyValue);
    }

    public int update(Object object, String[] fieldNames) {
        Class clazz = object.getClass();
        List<Object> params = new ArrayList<>();
        TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
        ColumnInfo priKey = tableInfo.getPrimaryKey();
        StringBuilder sql = new StringBuilder("UPDATE " + tableInfo.getTableName() + " SET");

        for (String fieldName : fieldNames) {
            Object fieldVlalue = ReflectUtil.invokeGetter(fieldName, object);
            params.add(fieldVlalue);
            sql.append(fieldName + "=?,");
        }
        params.add(ReflectUtil.invokeGetter(priKey.getColName(), object));

        sql.setCharAt(sql.length() - 1, ' ');
        sql.append(" WHERE ");
        sql.append(priKey.getColName() + "=? ");
        log.info("Execute sql:" + sql.toString());

        return this.execute(sql.toString(), params.toArray());
    }

    public List queryRows(String sql, final Class clazz, Object[] params) {
        return (List) executeQueryTemplate(sql, params, clazz, (connection, statement, rs) -> {
            ResultSetMetaData metaData;
            List list = null;
            try {
                metaData = rs.getMetaData();
                while (rs.next()) {
                    if (list == null)
                        list = new ArrayList();
                    Object rowObj = clazz.newInstance();
                    for (int i = 0; i < metaData.getColumnCount(); i++) {
                        String columnName = metaData.getColumnLabel(i + 1);
                        Object columnValue = rs.getObject(i + 1);
                        ReflectUtil.invokeSetter(rowObj, columnName, columnValue);
                    }
                    list.add(rowObj);
                }
            } catch (SQLException | IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
            }
            return list;
        });
    }

    public Object queryUniqueRows(String sql, Class clazz, Object[] params) {
        List list = queryRows(sql, clazz, params);
        return (list == null || list.size() == 0) ? null : list.get(0);
    }

    private Object queryValue(String sql, Object[] params) {
        return executeQueryTemplate(sql, params, null, new CallBack() {
            @Override
            public Object doExecute(Connection connection, PreparedStatement statement, ResultSet rs) {
                Object value = null;
                try {
                    while (rs.next())
                        value = rs.getObject(1);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                return value;
            }
        });
    }

    public Number queryNumber(String sql, Object[] params) {
        return (Number) queryValue(sql, params);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
